-module(pixbuf).
-behaviour(gen_server).

%% gen_server exports
-export([init/1, handle_call/3, handle_cast/2]).

-export([build/1, update_pixel/2, get_next_pixel/1, size/1, split_pixel/1]).

-record(pixbuf, {pixels, width=0, height=0, current={0,0} }).
-record(pixel, {
            x=0,
            y=0,
            color=undefined
       }).

%% gen_server
init(Size) ->
    Buf = build(Size),
    {Width, Height} = Size,
    ui:set_size(Width, Height),
    error_logger:info_msg("~p pixel buffer ready~n", [len(Buf)]),
    random:seed(now()),
    {ok, Buf}.

handle_call(get_size, _From, Buf) ->
    {reply, {Buf#pixbuf.width, Buf#pixbuf.height}, Buf};
handle_call(get_next, _From, Buf) ->
    {Current, Pixel} = get_next(Buf),
    {reply, Pixel, Buf#pixbuf{current=Current}}.

handle_cast({update_pixel, {X, Y, C}}, Buf=#pixbuf{pixels=Pixels}) ->
    OldPixel = get_pixel({X, Y}, Pixels),
    NewPixel = OldPixel#pixel{color=update_color(OldPixel#pixel.color, C)},
    NewPixels = set_pixel({X, Y}, NewPixel, Pixels),
    ui:set_pixel({X, Y, NewPixel#pixel.color}),
    
    {noreply, Buf#pixbuf{pixels=NewPixels}}.

%% external interface
size(Pid) -> gen_server:call(Pid, get_size).

get_next_pixel(Pid) -> gen_server:call(Pid, get_next, infinity).

update_pixel(Pid, Px) -> gen_server:cast(Pid, {update_pixel, Px}).

split_pixel(Pixel) -> {Pixel#pixel.x, Pixel#pixel.y, Pixel#pixel.color}.


%% Internal
len(Buf) -> Buf#pixbuf.width*Buf#pixbuf.height.

update_color(Color1, undefined) -> Color1;
update_color(undefined, Color2) -> Color2;
update_color(Color1, Color2) -> color:avg(Color1, Color2).

build({W, H}) ->
    random:seed(now()),
    Row = array:new([{size, W}, {default, #pixel{}}, {fixed, true}]),
    Pixels = array:new([{size, H}, {default, Row}, {fixed, true}]),
    #pixbuf{pixels=init_pixels(0, 0, W, H, Pixels), width=W, height=H}.

init_pixels(_X, Y, _W, H, Pixels) when Y >= H -> Pixels;
init_pixels(X, Y, W, H, Pixels) when X >= W -> init_pixels(0, Y+1, W, H, Pixels);
init_pixels(X, Y, W, H, Pixels) ->
    Pixel = get_pixel({X, Y}, Pixels),
    NewPixel = Pixel#pixel{x=X, y=Y},
    init_pixels(X+1, Y, W, H, set_pixel({X, Y}, NewPixel, Pixels)).

set_pixel({X, Y}, Pixel, Pixels) ->
    Row = array:get(Y, Pixels),
    NewRow = array:set(X, Pixel, Row),
    array:set(Y, NewRow, Pixels).

get_pixel({X, Y}, Pixels) ->
    Row = array:get(Y, Pixels),
    array:get(X, Row).

get_next(Buf) ->
    {X, Y} = Buf#pixbuf.current,
    Pixels = Buf#pixbuf.pixels,
    Size = {Buf#pixbuf.width, Buf#pixbuf.height},
    get_next({X+1, Y}, Size, Pixels).

get_next({X, Y}, {W, H}, Pixels) when X == W andalso Y == (H-1) -> get_next({0, 0}, {W, H}, Pixels);
get_next({X, Y}, {W, H}, Pixels) when X == W -> get_next({0, Y+1}, {W, H}, Pixels);
get_next({X, Y}, _Size, Pixels) -> {{X, Y}, get_pixel({X, Y}, Pixels)}.

